<!DOCTYPE HTML>
<html lang="zh-Hans" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>译：Tokio 内部机制 - Rust 优秀博文</title>


        <!-- Custom HTML head -->
        
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="../../favicon.svg">
        <link rel="shortcut icon" href="../../favicon.png">
        <link rel="stylesheet" href="../../css/variables.css">
        <link rel="stylesheet" href="../../css/general.css">
        <link rel="stylesheet" href="../../css/chrome.css">
        <link rel="stylesheet" href="../../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="../../fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../../highlight.css">
        <link rel="stylesheet" href="../../tomorrow-night.css">
        <link rel="stylesheet" href="../../ayu-highlight.css">

        <!-- Custom theme stylesheets -->

    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "../../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">1.</strong> 类型系统</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow.html"><strong aria-hidden="true">1.1.</strong> Rust Concept Clarification Deref vs AsRef vs Borrow vs Cow</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Deref_AsRef_Borrow_Cow释义.html"><strong aria-hidden="true">1.2.</strong> Deref AsRef Borrow Cow 释义</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然.html"><strong aria-hidden="true">1.3.</strong> Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Cow类型有什么用？详解Cow及其用途/Rust的Cow类型有什么用？详解Cow及其用途.html"><strong aria-hidden="true">1.4.</strong> Rust的Cow类型有什么用？详解Cow及其用途</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/判别Fn、FnMut、FnOnce的标准/判别Fn、FnMut、FnOnce的标准.html"><strong aria-hidden="true">1.5.</strong> 判别Fn、FnMut、FnOnce的标准</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/一行代码告诉你内部可变性的真相(UnsafeCell)/一行代码告诉你内部可变性的真相(UnsafeCell).html"><strong aria-hidden="true">1.6.</strong> 一行代码告诉你内部可变性的真相(UnsafeCell)</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Tour_of_Rust's_Standard_Library_Traits/Tour_of_Rust's_Standard_Library_Traits.html"><strong aria-hidden="true">1.7.</strong> Tour of Rust's Standard Library Traits</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/逆变、协变与子类型，以及Rust/逆变、协变与子类型，以及Rust.html"><strong aria-hidden="true">1.8.</strong> 逆变、协变与子类型，以及Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust自引用结构、Pin与Unpin/Rust自引用结构、Pin与Unpin.html"><strong aria-hidden="true">1.9.</strong> Rust自引用结构、Pin与Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/为什么Rust需要Pin,Unpin/为什么Rust需要Pin,Unpin.html"><strong aria-hidden="true">1.10.</strong> 译：为什么 Rust 需要 Pin, Unpin ？</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/定海神针Pin和Unpin/定海神针Pin和Unpin.html"><strong aria-hidden="true">1.11.</strong> 译：定海神针 Pin 和 Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/sizedness-in-rust/sizedness-in-rust.html"><strong aria-hidden="true">1.12.</strong> Sizedness in Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust生命周期集大成者PhantomData〈T〉/Rust生命周期集大成者PhantomData〈T〉.html"><strong aria-hidden="true">1.13.</strong> Rust生命周期集大成者 PhantomData&lt;T&gt;</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操_Part_0.html"><strong aria-hidden="true">1.14.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操 Part 0</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：GAT实现引用类型关联_Part_1.html"><strong aria-hidden="true">1.15.</strong> 数据库表达式执行的黑魔法：GAT实现引用类型关联 Part 1</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用HRTB写bound_Part_2.html"><strong aria-hidden="true">1.16.</strong> 数据库表达式执行的黑魔法：用HRTB写bound Part 2</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码_Part_3_&_4.html"><strong aria-hidden="true">1.17.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码 Part 3 & 4</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化_Part_5_&_6.html"><strong aria-hidden="true">1.18.</strong> 数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化 Part 5 & 6</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型_Part_7.html"><strong aria-hidden="true">1.19.</strong> 数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型 Part 7</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">2.</strong> 生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../02_生命周期/Rust中的生命周期——从StrSplit实例说开去/Rust中的生命周期——从StrSplit实例说开去.html"><strong aria-hidden="true">2.1.</strong> Rust中的生命周期——从StrSplit实例说开去</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/与ChatGPT深度对话来学Rust生命周期/与ChatGPT深度对话来学Rust生命周期.html"><strong aria-hidden="true">2.2.</strong> 与ChatGPT深度对话来学Rust生命周期</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（1）/进击的Rust生命周期——early_bound与late_bound（1）.html"><strong aria-hidden="true">2.3.</strong> 进击的Rust生命周期——early_bound与late_bound（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（2）/进击的Rust生命周期——early_bound与late_bound（2）.html"><strong aria-hidden="true">2.4.</strong> 进击的Rust生命周期——early_bound与late_bound（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（1）/进击的Rust生命周期——一力降十会的MIR（1）.html"><strong aria-hidden="true">2.5.</strong> 进击的Rust生命周期——一力降十会的MIR（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（2）/进击的Rust生命周期——一力降十会的MIR（2）.html"><strong aria-hidden="true">2.6.</strong> 进击的Rust生命周期——一力降十会的MIR（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Common_Rust_Lifetime_Misconceptions/Common_Rust_Lifetime_Misconceptions.html"><strong aria-hidden="true">2.7.</strong> Common Rust Lifetime Misconceptions</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Rust生命周期常见误区/Rust生命周期常见误区.html"><strong aria-hidden="true">2.8.</strong> 译：Rust生命周期常见误区</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">3.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../05_无畏并发/简单写个Rust无锁队列/简单写个Rust无锁队列.html"><strong aria-hidden="true">3.1.</strong> 简单写个Rust无锁队列</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——混合自旋锁/进击的Rust多线程——混合自旋锁.html"><strong aria-hidden="true">3.2.</strong> 进击的Rust多线程——混合自旋锁</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/An_unsafe_tour_of_Rust's_Send_and_Sync/An_unsafe_tour_of_Rust's_Send_and_Sync.html"><strong aria-hidden="true">3.3.</strong> An unsafe tour of Rust's Send and Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——Send与Sync/进击的Rust多线程——Send与Sync.html"><strong aria-hidden="true">3.4.</strong> 进击的Rust多线程——Send与Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——离经叛道的PhantomData/进击的Rust多线程——离经叛道的PhantomData.html"><strong aria-hidden="true">3.5.</strong> 进击的Rust多线程——离经叛道的PhantomData</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust_Async_Pin概念解析/Rust_Async_Pin概念解析.html"><strong aria-hidden="true">3.6.</strong> Rust Async: Pin概念解析</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust和C++的并发库对比/Rust和C++的并发库对比.html"><strong aria-hidden="true">3.7.</strong> 译：Rust 和 C++ 的并发库对比</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust原子类型和内存排序/Rust原子类型和内存排序.html"><strong aria-hidden="true">3.8.</strong> Rust原子类型和内存排序</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">4.</strong> 网络编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../06_网络编程/从编解码层面理解WebSocket_手写一个WebSocket/从编解码层面理解WebSocket_手写一个WebSocket.html"><strong aria-hidden="true">4.1.</strong> 从编解码层面理解WebSocket 手写一 个WebSocket</a></li><li class="chapter-item expanded "><a href="../../06_网络编程/透过Rust探索系统的本原：网络篇/透过Rust探索系统的本原：网络篇.html"><strong aria-hidden="true">4.2.</strong> 透过Rust探索系统的本原：网络篇</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">5.</strong> 轮子系列</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../07_轮子系列/700行Rust写一个内存分配器/700行Rust写一个内存分配器.html"><strong aria-hidden="true">5.1.</strong> 700行Rust写一个内存分配器</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust：网络库的实现思路/Rust：网络库的实现思路.html"><strong aria-hidden="true">5.2.</strong> Rust：网络库的实现思路</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust异步运行时基础部件/Rust异步运行时基础部件.html"><strong aria-hidden="true">5.3.</strong> Rust异步运行时基础部件</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（1）.html"><strong aria-hidden="true">5.4.</strong> 使用Rust+epoll编写异步IO框架（1）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（2）.html"><strong aria-hidden="true">5.5.</strong> 使用Rust+epoll编写异步IO框架（2）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（3）.html"><strong aria-hidden="true">5.6.</strong> 使用Rust+epoll编写异步IO框架（3）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day1.html"><strong aria-hidden="true">5.7.</strong> 用rust从零开发一套web框架：day1</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day2.html"><strong aria-hidden="true">5.8.</strong> 用rust从零开发一套web框架：day2</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day3.html"><strong aria-hidden="true">5.9.</strong> 用rust从零开发一套web框架：day3</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day4.html"><strong aria-hidden="true">5.10.</strong> 用rust从零开发一套web框架：day4</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day5.html"><strong aria-hidden="true">5.11.</strong> 用rust从零开发一套web框架：day5</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">6.</strong> 奇技淫巧</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Copy-On-Write是不是优化？/Copy-On-Write是不是优化？.html"><strong aria-hidden="true">6.1.</strong> 译：Copy-On-Write是不是优化？</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/揭秘神奇的Rust_Axum风格的函数实现/揭秘神奇的Rust_Axum风格的函数实现.html"><strong aria-hidden="true">6.2.</strong> 译：揭秘神奇的 Rust Axum 风格的函数实现</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html"><strong aria-hidden="true">6.3.</strong> “变长参数”函数与回调</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Rust字符串格式化的幕后：format_args!()/Rust字符串格式化的幕后：format_args!().html"><strong aria-hidden="true">6.4.</strong> 译：Rust字符串格式化的幕后：format_args!()</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html"><strong aria-hidden="true">6.5.</strong> 给Rust带来一点C++特产</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/一步步实现_Rust_Bevy_ECS_的_System_简化版本/一步步实现_Rust_Bevy_ECS_的_System_简化版本.html"><strong aria-hidden="true">6.6.</strong> 一步步实现 Rust Bevy ECS 的 System 简化版本</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples.html"><strong aria-hidden="true">6.7.</strong> Exploring Design Patterns in Rust with Algorithmic Trading Examples</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">7.</strong> 源码分析</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：bytes源码分析/Rust并发：bytes源码分析.html"><strong aria-hidden="true">7.1.</strong> Rust并发：bytes源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库Arc源码分析/Rust并发：标准库Arc源码分析.html"><strong aria-hidden="true">7.2.</strong> Rust并发：标准库Arc源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库sync_Once源码分析/Rust并发：标准库sync_Once源码分析.html"><strong aria-hidden="true">7.3.</strong> Rust并发：标准库sync::Once源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：引用计数Rc/Rust源码阅读：引用计数Rc.html"><strong aria-hidden="true">7.4.</strong> Rust源码阅读：引用计数Rc</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：Cell、RefCell与内部可变性/Rust源码阅读：Cell、RefCell与内部可变性.html"><strong aria-hidden="true">7.5.</strong> Rust源码阅读： Cell、RefCell与内部可变性</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/关于_Rust_的_UnsafeCell、Cell_与_RefCell/关于_Rust_的_UnsafeCell、Cell_与_RefCell.html"><strong aria-hidden="true">7.6.</strong> 关于 Rust 的 UnsafeCell、Cell 与 RefCell</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Async_async-stream源码分析/Rust_Async_async-stream源码分析.html"><strong aria-hidden="true">7.7.</strong> Rust Async: async-stream源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/走进Tokio的异步世界/走进Tokio的异步世界.html"><strong aria-hidden="true">7.8.</strong> 走进 Tokio 的异步世界</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/tokio.rs_runtime的实现/tokio.rs_runtime的实现.html"><strong aria-hidden="true">7.9.</strong> tokio.rs runtime 的实现</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/Tokio_internals.html"><strong aria-hidden="true">7.10.</strong> Tokio internals</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/译文：Tokio內部机制.html" class="active"><strong aria-hidden="true">7.11.</strong> 译：Tokio 内部机制</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html"><strong aria-hidden="true">7.12.</strong> Rust Axum HTTP 框架的架构分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/安利一个Rust_Game_Engine：Bevy--ECS部分/安利一个Rust_Game_Engine：Bevy--ECS部分.html"><strong aria-hidden="true">7.13.</strong> 安利一个Rust Game Engine：Bevy--ECS部分</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_解析之任务调度/Tokio_解析之任务调度.html"><strong aria-hidden="true">7.14.</strong> Tokio 解析之任务调度</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">8.</strong> 生态观察</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html"><strong aria-hidden="true">8.1.</strong> Rust web frameworks have subpar error reporting</a></li><li class="chapter-item expanded "><a href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html"><strong aria-hidden="true">8.2.</strong> SeaORM：要做Rust版本的ActiveRecord</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">9.</strong> 死灵终极</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_总述.html"><strong aria-hidden="true">9.1.</strong> 译：Learn Rust the Dangerous Way 总述</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_0.html"><strong aria-hidden="true">9.2.</strong> 译：Learn Rust the Dangerous Way 0</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_1.html"><strong aria-hidden="true">9.3.</strong> 译：Learn Rust the Dangerous Way 1</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_2.html"><strong aria-hidden="true">9.4.</strong> 译：Learn Rust the Dangerous Way 2</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_3.html"><strong aria-hidden="true">9.5.</strong> 译：Learn Rust the Dangerous Way 3</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_4.html"><strong aria-hidden="true">9.6.</strong> 译：Learn Rust the Dangerous Way 4</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_5.html"><strong aria-hidden="true">9.7.</strong> 译：Learn Rust the Dangerous Way 5</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（一）.html"><strong aria-hidden="true">9.8.</strong> Unsafe Rust 随堂小测（一）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（二）.html"><strong aria-hidden="true">9.9.</strong> Unsafe Rust 随堂小测（二）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（三）.html"><strong aria-hidden="true">9.10.</strong> Unsafe Rust 随堂小测（三）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测参考答案.html"><strong aria-hidden="true">9.11.</strong> Unsafe Rust 随堂小测参考答案</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Rust 优秀博文</h1>

                    <div class="right-buttons">
                        <a href="../../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="譯tokio-內部機制從頭理解-rust-非同步-io-框架"><a class="header" href="#譯tokio-內部機制從頭理解-rust-非同步-io-框架">【譯】Tokio 內部機制：從頭理解 Rust 非同步 I/O 框架</a></h1>
<p>译文：<a href="https://weihanglo.tw/posts/2018/tokio-internals">https://weihanglo.tw/posts/2018/tokio-internals</a></p>
<blockquote>
<p>本文譯自 <a href="https://cafbit.com/post/tokio_internals/">Tokio internals: Understanding Rust’s asynchronous I/O framework from the bottom up</a>。
Thanks <a href="https://www.davidsimmons.com/">David Simmons</a> for this awesome article!</p>
</blockquote>
<p><a href="https://tokio.rs/">Tokio</a> 是 Rust 的開發框架，用於開發非同步 I/O 程式（asynchronous I/O，一種事件驅動的作法，可實現比傳統同步 I/O 更好的延伸性、效能與資源利用）。可惜的是，Tokio 過於精密的抽象設計，招致難以學習的惡名。即使我讀完教程後，依然不認為自己充分內化這些抽象層，以便推斷實際發生的事情。</p>
<p>從前的非同步 I/O 相關開發經驗甚至阻礙我學習 Tokio。我習慣使用作業系統提供的 selection 工具（例如 Linux epoll）當作起點，再轉移至 dispatch、state machine 等等。倘若直接從 Tokio 抽象層出發，卻沒有清楚了解 <code>epoll_wait()</code> 在何處及如何發生，我會覺得難以連結每個概念。Tokio 與 future-driven 的方法就好像一個黑盒子。</p>
<p>我決定不繼續由上而下的方法學習 Tokio，反其道而行，而是透過閱讀原始碼，確切理解具體實作是如何驅動從 epoll 事件到 <code>Future::poll()</code> 消耗 I/O 的整個過程。我不會深入高層次的 Tokio 與 futures 使用細節，<a href="https://tokio.rs/docs/getting-started/tokio/">現有的教程</a> 有更完整詳細的內容。除了簡短的小結，我也不會探討一般性的非同步 I/O 問題，畢竟這些問題都可寫個獨立的主題了。我的目標是有信心讓 futures 與 Tokio 以我所認知的方式執行。</p>
<p>首先，有些重要的聲明。請注意，Tokio 正快速開發中，這裡所見所聞可能不久就會過時。這個研究中我用了 <code>tokio-core 0.1.10</code>、<code>futures- 0.1.17</code> 與 <code>mio 0.6.10</code>。由於我想從最底層理解 Tokio，我並不會考慮更高層次的套件如 <code>tokio-proto</code> 與 <code>tokio-service</code>。tokio-core 的事件系統本身有許多細節，為了精簡，我會盡量避開這些細項。我在 Linux 作業系統上研究 Tokio，而有些討論細節與作業系統相依，如 epoll。最後，這裡所有東西都是我這個 Tokio 新手的詮釋，可能會有錯誤或誤導。</p>
<h2 id="asynchronous-io-in-a-nutshell"><a class="header" href="#asynchronous-io-in-a-nutshell">Asynchronous I/O in a nutshell</a></h2>
<p>同步 I/O 程式會執行阻塞性的 I/O 操作，直到操作完成。例如讀取會阻塞至資料抵達，寫入會阻塞線程直到欲傳遞的 bytes 送達 kernel。這些操作非常適合依序執行的傳統命令式程式設計。舉例來說，一個 HTTP 伺服器替每個新連線產生一個新線程，這個線程會讀取資料並阻塞線程直到接收完整的 request，之後處理請求，再來阻塞線程至資料完全寫入 response。這是個方法非常直觀，缺點是會阻塞線程，因此每個連線的線程要各自獨立，每個線程也需有自己的 stack。然而，線程開銷阻礙了伺服器處理大量連線的可延伸性（參閱 <a href="https://wikipedia.org/wiki/C10k_problem">C10k problem</a>)，對低階系統來說也不易負荷。</p>
<p>如果 HTTP server 使用非同步 I/O 開發，換句話說，在同一個線程上處理所有 I/O 操作。如此一來，所有活躍的連線以及 socket 監聽都會配置為非阻塞狀態（non-blocking），並在 event loop 中監控讀取與寫入是否就緒，進而在事件發生時分派給對應的處理程式（handler）。而每個連線都需維護自身的狀態與 buffer，如果一個處理程式一次僅能從 200 bytes 的 request 中讀取 100 個位元組（bytes），它就不能等待剩下的 bytes 而造成線程阻塞，處理程式必須將部分資料儲存在 buffer 中，設定當前的狀態為「讀取請求中」，並返回給 event loop。待到下一次連線調用的相同的處理程式，它才可讀取剩餘的 bytes 並將狀態轉為「寫入回應中」。如此的資源管理系統將會非常迅速，但同時也產生更複雜的 state machine 與容易出錯的毛病。</p>
<p>理想中的非同步 I/O 框架應該要提供能寫出近似於同步 I/O 的程式，但底層是 event loop 與 state machine。這對每個語言來說都很不容易，不過 Tokio 的實現已接近了。</p>
<h2 id="the-tokio-stack"><a class="header" href="#the-tokio-stack">The Tokio stack</a></h2>
<p><img src="assets/010.svg" alt="" /></p>
<p>Tokio 的技術棧由下列幾個部分組成：</p>
<ol>
<li><strong>The system selector</strong>。每個作業系統皆提供接收 I/O 事件的工具，如 epoll（linux）、<code>kqueue()</code>（FreeBSD/macOS），與 IOCP（Windows）。</li>
<li><strong>Mio - Metal I/O</strong>。<a href="https://docs.rs/mio/0.6.10/mio/">Mio</a> 是一個 Rust crate，提供低階通用的 I/O API，內部處理特定作業系統的 selector 實作細節，所以你不需再處理這件事。</li>
<li><strong>Futures</strong>。<a href="https://docs.rs/futures/0.1.17/futures/">Futures</a> 以強大的抽象來表示尚未發生的事物。這些 future 以許多好用的方式組合成另一新的複合 future 來代表一系列複雜的事件。這個抽象層足以通用於許多 I/O 之外的事件，但在 Tokio 中 ，我們專注在利用 futures 開發非同步 I/O state machines。</li>
<li><strong>Tokio</strong>。<a href="https://docs.rs/tokio-core/0.1.10/tokio_core/">tokio-core</a> 提供一個中心的 event loop，這個 event loop 整合 Mio 回應 I/O 事件，並驅動 futures 完成（completion）。</li>
<li><strong>Your program</strong>。一個採用 Tokio 框架的程式，會以 futures 操作非同步 I/O，並將這些 futures 傳遞給 Tokio 的 event loop 來執行。</li>
</ol>
<h2 id="mio-metal-io"><a class="header" href="#mio-metal-io">Mio: Metal I/O</a></h2>
<p>Mio 旨在提供一系列低階的 I/O API，允許調用端接收事件，如 socket 讀寫就緒狀態（readiness state）改變等。重點如下：</p>
<ol>
<li><strong>Poll 與 Evented</strong>。Mio 提供 <a href="https://docs.rs/mio/0.6.10/mio/event/trait.Evented.html"><code>Evented</code></a> trait 來表示任何可當作事件來源的事物。在你的 event loop 中，你會利用 <a href="https://docs.rs/mio/0.6.10/mio/struct.Poll.html"><code>mio::Poll</code></a> 物件註冊一定數量的 <code>Evented</code>，再調用 <a href="https://docs.rs/mio/0.6.10/mio/struct.Poll.html#method.poll"><code>mio::Poll::poll</code></a> 來阻塞 loop，直到一至多個 <code>Evented</code> 產生事件（或超時）。</li>
<li><strong>System selector</strong>。Mio 提供可跨平台的 system selector 訪問，所以 Linux epoll、Windows IOCP、FreeBSD/macOS <code>kqueue()</code>，甚至許多有潛力的平台都可調用相同的 API。不同平台使用 Mio API 的開銷不盡相同。由於 Mio 是提供基於 readiness（就緒狀態）的 API，與 Linux epoll 相似，不少 API 在 Linux 上都可以一對一映射。（例如：<code>mio::Events</code> 實質上是一個 <code>struct epoll_event</code> 陣列。）對比之下，Windows IOCP 是基於完成（completion-based）而非基於 readiness 的 API，所以兩者間會需要較多橋接。Mio 同時提供自身版本的 <code>std::net</code> struct 如 <code>TcpListener</code>、<code>TcpStream</code> 與 <code>UdpSocket</code>。這些 API 封裝 <code>std::net</code> 版本的 API，預設為非阻塞且提供 <code>Evented</code> 實作讓其將 socket 加入 system selector。</li>
<li><strong>Non-system events</strong>。Mio 除了提供從 I/O 所得的 readiness 狀態來源，也可以用來指示從 user-space 來的 readiness 事件（非系統事件）。舉例來說，當一個工作線程（worker thread）完成一單位的工作，它就可以向 event loop 發出完成信號。你的程式調用 <a href="https://docs.rs/mio/0.6.10/mio/struct.Registration.html#method.new2"><code>Registration::new2()</code></a> 以取得一個 <code>(Registration, SetReadiness)</code> 元組。<code>Registration</code> 是一個實作 <code>Evented</code> 且藉由 Mio 註冊在 event loop 的物件；而需要指示當前 readiness 狀態時，則會調用 <a href="https://docs.rs/mio/0.6.10/mio/struct.SetReadiness.html#method.set_readiness"><code>SetReadiness::set_readiness</code></a>。在 Linux 上，非系統事件通知以 pipe 實作，當調用 <code>SetReadiness::set_readiness()</code> 時，會將 <code>0x01</code> 這個位元組寫入 pipe 中。而 <code>mio::Poll</code> 底層的 epoll 會配置為監控 pipe 讀取結束，所以 <code>epoll_wait()</code> 會解除阻塞，而 Mio 就可以將事件傳遞到調用端。另外，無論註冊多少非系統事件，都只會在 Poll 實例化時建立唯一一個 pipe。</li>
</ol>
<p>每個 <code>Evented</code> 的註冊皆與一個由調用端提供 <code>usize</code> 型別的 <a href="https://docs.rs/mio/0.6.10/mio/struct.Token.html"><code>mio::Token</code></a> 綁定，這個 token 將會與事件一起返回，以指示出對應的註冊資訊。這種作法很好地映射到 Linux 的 system selector，因為 token 可以放置在 64-bit 的 <code>epoll_data</code> union 中，並保持相同的功能。</p>
<p>這裡提供一個 Mio 操作的實際案例，下面是我們在 Linux 上使用 Mio 監控一個 UDP socket 的情況：</p>
<ol>
<li>
<p><strong>建立 socket</strong>。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let socket = mio::net::UdpSocket::bind(
    &amp;SocketAddr::new(
        std::net::IpAddr::V4(std::net::Ipv4Addr::new(127,0,0,1)),
        2000
    )
).unwrap();
<span class="boring">}
</span></code></pre></pre>
<p>建立一個 Linux UDP socket，其中封裝一個 <code>std::net::UdpSocket</code>，再封裝在 <code>mio::net::UdpSocket</code> 中。這個 socket 為非阻塞性（non-blocking）。</p>
</li>
<li>
<p><strong>建立 poll 實例</strong>。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let poll = mio::Poll::new().unwrap();
<span class="boring">}
</span></code></pre></pre>
<p>在這步驟，Mio 初始化 system selector、readiness 佇列（用於非系統事件），以及併發保護。當 readiness 佇列初始化時，會建立一個 pipe，讓 readiness 從 user-space 發出信號，而這個 pipe 的檔案描述符（file descriptor）會加入 epoll 中。每個 <code>Poll</code> 物件建立時，都會賦予一個獨特、遞增的 <code>selector_id</code>。</p>
</li>
<li>
<p><strong>透過 poll 註冊 socket</strong>。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>poll.register(
    &amp;socket,
    mio::Token(0),
    mio::Ready::readable(),
    mio::PollOpt::level()
).unwrap();
<span class="boring">}
</span></code></pre></pre>
<p><code>UdpSocket</code> 的 <code>Evented::register()</code> 被調用時，會將代理指向一個封裝的 <code>EventedFd</code>，這個 <code>EventedFd</code> 會將 socket 的 file descriptor 加入 poll selector 中（最終會調用 <code>epoll_ctl(fepd, EPOLL_CTL_ADD, fd, &amp;epoll_event)</code>，而 <code>epoll_event.data</code> 設置為傳入的 token 值）。當一個 <code>UdpSocket</code> 註冊後，<code>selector_id</code> 會設置到與傳入的 <code>Poll</code> 相同，從而與 selector 產生連結。</p>
</li>
<li>
<p><strong>在 event loop 中呼叫 <code>poll()</code></strong>。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>loop {
    poll.poll(&amp;mut events, None).unwrap();
    for event in &amp;events {
        handle_event(event);
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>System selector（<code>epoll_wait()</code>）與 readiness 佇列將會輪詢（poll）新的事件。（<code>epoll_wait()</code> 會阻塞，但由於非系統事件是透過 pipe 出發 epoll，事件仍會即時處理。）這一系列組合的事件可供調用端處理。</p>
</li>
</ol>
<h2 id="futures-and-tasks"><a class="header" href="#futures-and-tasks">Futures and Tasks</a></h2>
<p><a href="https://wikipedia.org/wiki/Futures_and_promises">Futures</a> 是從函數式程式設計借來的技術，一個尚未完成的運算會以一個 future 代表，而這些獨立的 future 可以組合起來，開發更複雜的系統。這個概念對非同步 I/O 非常中用，因為在處理交易（transaction）的所有基礎步驟，都可以模化為合成 futures（combinded futures）。以 HTTP 伺服器為例，一個 future 讀取 request，會從接收到有效資料開始讀取到 request 結束，另一個 future 則會處理這個 request 並產生 response，再另一個 future 則會寫入 responses。</p>
<p>在 Rust 中，<a href="https://docs.rs/futures/0.1.17/futures/">futures crate</a> 實現了 futures。你可以透過實作 <a href="https://docs.rs/futures/0.1.17/futures/future/trait.Future.html">Future</a> trait 來定義自己的 future，這個 trait 需實現 <a href="https://docs.rs/futures/0.1.17/futures/future/trait.Future.html#tymethod.poll"><code>poll()</code></a> 方法，這個方法會在需要時調用，允許 future 開始執行。<code>poll()</code> 方法會回傳一個錯誤（error），或回傳一個指示告知 future 仍在處理，或是當 future 完成時返回一個值。<code>Future</code> trait 也提供許多組合操作子（combinator）作為預設方法。</p>
<p>欲理解 futures，須先探討三個重要的概念：<strong>tasks</strong>、<strong>executors</strong>，以及 <strong>notifications</strong>，且需理解此三者該如何安排，才能在正確時間點調用 future 的 <code>poll()</code> 方法。每一個 future 都在一個 task 語彙環境（context）中執行。一個 task 只與一個 future 關聯，而這個 future 卻可能是一個合成的 future，驅動其他封裝的 futures。（舉例來說，多個 future 用 <code>join_all()</code> 組合操作子，串連成單一一個 future，或是兩個 future 利用 <code>and_then()</code> 組合操作子來依序執行。）</p>
<p>Task 與它的 futures 需要被一個 <em>executor</em> 執行。一個 executor 的責任是在正確時間點輪詢 task/future，輪詢通常會在接收到執行進度開始的通知時。而這個通知將在一個實作 <a href="https://docs.rs/futures/0.1.17/futures/executor/trait.Notify.html"><code>futures::executor::Notify</code></a> trait 的物件調用 <a href="https://docs.rs/futures/0.1.17/futures/executor/trait.Notify.html"><code>notify</code></a> 時發布。這裡有個例子，是由 futures crate 所提供的非常簡單的 executor，在調用 future 上的 <a href="https://docs.rs/futures/0.1.17/futures/future/trait.Future.html#method.wait"><code>wait()</code></a> 被呼叫。擷自<a href="https://github.com/alexcrichton/futures-rs/blob/0.1.17/src/task_impl/std/mod.rs#L233">原始碼</a>：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Waits for the internal future to complete, blocking this thread's
/// execution until it does.
///
/// This function will call `poll_future` in a loop, waiting for the future
/// to complete. When a future cannot make progress it will use
/// `thread::park` to block the current thread.
pub fn wait_future(&amp;mut self) -&gt; Result&lt;F::Item, F::Error&gt; {
    ThreadNotify::with_current(|notify| {

        loop {
            match self.poll_future_notify(notify, 0)? {
                Async::NotReady =&gt; notify.park(),
                Async::Ready(e) =&gt; return Ok(e),
            }
        }
    })
}
<span class="boring">}
</span></code></pre></pre>
<p>給定一個融合 task 與 future 的 <a href="https://docs.rs/futures/0.1.17/futures/executor/struct.Spawn.html"><code>futures::executor::Spawn</code></a> 物件，這個 executor 在迴圈中調用 <a href="https://docs.rs/futures/0.1.17/futures/executor/struct.Spawn.html#method.poll_future_notify"><code>poll_future_notify</code></a>。這個 <code>Notify</code> 會成為 task 執行語彙環境的一部分，future 也會被輪詢。如果一個 future <code>poll</code> 方法回傳 <code>Async::NotReady</code>，表示 future 仍等待中，必須在往後再次輪詢。<code>Notify</code> object 會從 <a href="https://docs.rs/futures/0.1.17/futures/task/fn.current.html"><code>futures::task::current()</code></a> 取得一個指向 task 的 handle，且在 future 有些進展時調用 <a href="https://docs.rs/futures/0.1.17/futures/task/struct.Task.html#method.notify"><code>notify()</code></a> 方法。（當一個 future 被輪詢時，與該 future 相關的 task 訊息將會儲存到 thread-local 中，thread-local 可以透過 <code>current()</code> 存取取得。）上例中，如果輪詢回傳 <code>Async::NotReady</code>，executor 會阻塞至接收到通知。也許 future 在其他線程運算，在完成時調用 <code>notify()</code>；或是 <code>poll()</code> 方法在返回 <code>Asynx::NotReady</code> 之前，自身直接調用了 <code>notify()</code>（後者並不常見，因為理論上一個 <code>poll()</code> 在返回之前應該持續取得進展）。</p>
<p>Tokio 的 event loop 行為上比簡單整合「 Mio 事件驅動 future 完成」來得精細。舉例來說，一個 Mio event 表示一個 socket 的 readiness（就緒狀態），最後會產生一個通知，足以告知相對應的 future 需要輪詢。</p>
<p>處理 future 時，Task 是最基礎的執行單元，且基本上就是<a href="https://en.wikipedia.org/wiki/Green_threads">綠色線程</a>，提供<a href="https://en.wikipedia.org/wiki/Cooperative_multitasking">協調式多工</a>，允許在同一個系統線程有多個執行語彙環境。當一個 task 無法有所進展，會讓處理器先處理其他可執行的 task。我們必須理解的是，「通知」會發生在 task 層級而非 future 層級。當一個 task 被通知時，它會輪詢它連結的最高層級的 future，這會導致任何或是全部的 child future 同樣被輪詢。例如，如果一個 task 最高層級的 future 是一個以 <a href="https://docs.rs/futures/0.1.17/futures/future/fn.join_all.html"><code>join_all</code></a> 組合的十個 future，而其中一個 future 安排要通知此一 task，則無論需不需要，全部十個 future 皆須接受輪詢。</p>
<h2 id="tokios-interface-with-mio"><a class="header" href="#tokios-interface-with-mio">Tokio’s interface with Mio</a></h2>
<p>Tokio 利用上述的 Mio 「非系統事件」，將 task 通知轉換為 Mio 的事件。在取得一個 Mio 的 (<code>Registration</code>、<code>SetReadiness</code>）元組後，Tokio 會將 <code>Registration</code>（一個 <code>Evented</code>）註冊至 Mio 的 poll （event loop）中，再將 <code>SetReadiness</code> 封裝在實作了 <code>Notify</code> trait 的 <code>MySetReadiness</code> 中。<a href="https://github.com/tokio-rs/tokio-core/blob/0.1.10/src/reactor/mod.rs#L791">原始碼</a>如下：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>struct MySetReadiness(mio::SetReadiness);

impl Notify for MySetReadiness {
    fn notify(&amp;self, _id: usize) {
        self.0.set_readiness(mio::Ready::readable())
              .expect(&quot;failed to set readiness&quot;);
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>在這個作法中，task 的通知將轉換為 Mio 事件，且可以透過 Tokio 的事件處理與分派機制與其他 Mio 事件作伙處理。</p>
<p>如同 Mio 封裝 <code>std::net</code> 內的 <code>UdpSocket</code>、<code>TcpListener</code>，以及 <code>TcpStream</code> 來客製化需求，Tokio 也利用了組合（composition）與裝飾（decoration）建立這些型別的 Tokio 版。舉例來說，Tokio 的 <code>UdpSocket</code> 架構大致如下：</p>
<p><img src="assets/020.svg" alt="" /></p>
<p>Tokio 版本的 I/O 來源型別的建構子都需要傳入 event loop 的 handle（<a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Handle.html"><code>tokio_core::reactor::Handle</code></a>）。當實例化時，這些型別會將它們的 socket 註冊至 Mio poll 的 event loop 上，以利接收 edge-triggred（譯注：一種 epoll event 的觸發模式）的事件及其新賦予的偶數數字 token（以下會解釋）。當底層的 I/O 操作回傳 <code>WouldBlock</code> 時，這些型別可以很方便地安排當前的 task 來接收讀寫的 readiness。</p>
<p>Tokio 在 Mio 上註冊了許多 <code>Evented</code> 型別，儲存在特定的 token 上：</p>
<ul>
<li>
<p><strong>Token 0（<code>TOKEN_MESSAGES</code>）</strong>：用於 Tokio 內部的消息佇列（message queue），這個佇列提供移除 I/O 來源、接收讀寫 readiness 通知的 task 排程，設定 timeout，以及執行在 event loop 語彙環境中的任意閉包。這個 token 可以安全地從其他線程與 event loop 溝通。例如，<a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Remote.html#method.spawn"><code>Remote::spawn()</code></a> 透過訊息系統，將 future 送達 event loop。</p>
<p>實作上，消息佇列是一個 <a href="https://docs.rs/futures/0.1.17/futures/sync/mpsc/index.html"><code>futures::sync::mpsc</code></a> stream。身為一個 <a href="https://docs.rs/futures/0.1.17/futures/stream/trait.Stream.html"><code>futures::stream::Stream</code></a>（與 future 類似，但是產生一序列的值而非單一值），消息佇列使用上述 <code>MySetReadiness</code> 方案來處理，而 <code>Registration</code> 則是以 <code>TOKEN_MESSAGES</code> 這個 token 註冊。當接收到 <code>TOKEN_MESSAGES</code> 事件時，該事件會分派到 <code>consume_queue()</code> 方法進一步處理。（原始碼：<a href="https://github.com/tokio-rs/tokio-core/blob/0.1.10/src/reactor/mod.rs#L133"><code>enum Message</code></a>、<a href="https://github.com/tokio-rs/tokio-core/blob/0.1.10/src/reactor/mod.rs#L403"><code>consume_queue()</code></a>）</p>
</li>
<li>
<p><strong>Token 1（<code>TOKEN_FUTURE</code>）</strong>：用於通知 Tokio 需要輪詢 main task。這個 token 會在與 main task 相關聯的通知上（也就是傳入 <code>Core::run()</code> 的 future 或它的子 future，而非透過 <code>spawn()</code> 在不同 task 中執行的 future）。這個事件同樣用了 <code>MySetReadiness</code> 方案將 future 轉譯成 Mio 的事件。在一個 future 被 main task 執行前，會先回傳 <code>Async::NotReady</code>，並以其所選的方式在稍後發布通知。當接收了 <code>TOKEN_FUTURE</code> 事件，Tokio event loop 就會再次輪詢 main task。</p>
</li>
<li>
<p><strong>大於 1 的偶數 token（<code>TOKEN_START + key \* 2</code>）</strong>：用來指示 I/O 來源的 readiness 改變。Token 中的 key 是 <code>Slab</code> key，關聯值是 <code>Core::inner::io_dispatch Slab&lt;ScheduledIo&gt;</code>。當 Mio 的 I/O 來源型別（<code>UdpSocket</code>、<code>TcpListener</code>、<code>TcpStream</code>）實例化之初，會自動以此 token 註冊。</p>
</li>
<li>
<p><strong>大於 1 的奇數 token（<code>TOKEN_START + key \* 2 + 1</code>）</strong>：用來指示一個 spawned task（及其關聯的 future）需要被輪詢。Token 中的 key 是 <code>Slab</code> key，關聯值是 <code>Core::inner::task_dispatch Slab&lt;ScheduledTask&gt;</code>。和 <code>TOKEN_MESSAGES</code> 與 <code>TOKEN_FUTURE</code> 事件相同，這個事件也用了 <code>MySetReadiness</code> 溝通。</p>
</li>
</ul>
<h2 id="tokio-event-loop"><a class="header" href="#tokio-event-loop">Tokio event loop</a></h2>
<p>Tokio，更精確來說是 <a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Core.html"><code>tokio_core::reactor::Core</code></a> 提供了 event loop 來管理 futures 和 tasks，驅動 future 完成，以及與 Mio 介接的介面，讓 I/O 事件可正確通知對應的 task。使用 event loop 需透過 <a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Core.html#method.new"><code>Core::new()</code></a> 實例化一個 <code>Core</code>，並調用 <a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Core.html#method.run"><code>Core::run()</code></a> 傳入一個 future。這個 event loop 在返回之前，將會驅動傳入的 future 至完成。以伺服器程式來說（serve application），這個 future 很可能生命週期較長，例如使用 <code>TcpListener</code> 持續接收新傳入的連結，每個連結透過 <a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Handle.html#method.spawn"><code>Handle.spawn()</code></a> 分別建立 task，由自身的 future 獨立處理。</p>
<p>以下的流程圖大略點出 Tokio event loop 的基本輪廓：</p>
<p><img src="assets/030.svg" alt="" /></p>
<h2 id="what-happens-when-data-arrives-on-a-socket"><a class="header" href="#what-happens-when-data-arrives-on-a-socket">What happens when data arrives on a socket?</a></h2>
<p>想了解 Tokio，可以觀察當資料抵達 socket 時，event loop 發生的每個步驟。我很訝異地發現，這個過程最終分為兩部分，分別在 event loop 內各自的迭代中，進行各自的 epoll 交易處理。第一部分負責當 socket 讀取就緒時（例如，Mio 事件帶著比 1 大的偶數 token，或 main task 的 <code>TOKEN_FUTURE</code>），傳送通知到對該 socket 有興趣的 task；第二部分則是透過輪詢 task 與它的 future 來處理通知（例如，Mio 事件帶著比 1 大的奇數 token）。我們來了解以下情境：一個 spawned task 從 Linux 上的 <code>UdpSocket</code>，透過 Tokio event loop 讀取資料，並假設前一次輪詢結果導致 <code>recv_from()</code> 回傳一個 <code>WouldBlock</code> 錯誤。</p>
<p><img src="assets/040.svg" alt="" /></p>
<p>Tokio event loop 調用 <code>mio::Poll:poll()</code>，該方法轉而調用 <code>epoll_wait()</code>（在 Linux 上）進而阻塞到某個監測中的 file descriptor 發生了 readiness 改變的事件。當上述情形發生後，<code>epoll_wait()</code> 回傳一個 <code>epoll_event</code> structs 的陣列，用以描述發生什麼事，這些 structs 也將透過 Mio 轉譯為 <code>mio::Events</code>，並返回 Tokio。（在 Linux 上，這些轉譯應該是零成本（zero-cost），因為 <code>mio::Events</code> 就只是簡單，以一個 <code>epoll_event</code> 陣列組成的元組結構（tuple struct）。）在我們的例子，假設在陣列中只有一個事件指出 socket 已讀取就緒。由於該事件的 token 是大於 1 的偶數，Tokio 辨識其為 I/O 事件，並從 <code>Slab&lt;ScheduledIo&gt;</code> 中尋找對應的元素，以取得有哪些 task 對這個 socket 的讀寫 readiness 狀態有興趣。接下來，Tokio 會通知對讀取有興趣的 task，這些 task 透過前述的 <code>MySetReadiness</code>，調用 Mio 的 <code>set_readiness()</code>。Mio 會將這個非系統的事件詳細資訊加到 readiness 佇列中，並寫入 <code>0x01</code> 到 readiness pipe 中。</p>
<p><img src="assets/050.svg" alt="" /></p>
<p>在 Tokio event loop 往下一個迭代前進之前，它會再次輪詢 Mio，Mio 則調用 <code>epoll_wait()</code>，而 <code>epoll_wait()</code> 這次返回一個在 Mio 的 readiness pipe 上發生的讀取 readiness 事件。Mio 讀取之前寫入的 <code>0x01</code>，並從 readiness 佇列取出最前端（dequeue）的非系統事件資料，並將這個事件回傳到 Tokio。由於該事件的 token 是大於 1 的奇數 token，Tokio 辨識其為 task 通知事件，並從 <code>Slab&lt;ScheduledTask&gt;</code> 中尋找對應的元素，以取得 task 從 <code>spawn()</code> 回傳的最原始的 <code>Spawn</code> 物件。接下來，Tokio 透過 <a href="https://docs.rs/futures/0.1.17/futures/executor/struct.Spawn.html#method.poll_future_notify"><code>poll_future_notify()</code></a> 輪詢這個 task 與它的 future，這個 future 可能會從 socket 讀取資料，直至得到 <code>WouldBlock</code> 錯誤。</p>
<p>這個二迭代的方法涉及了 pipe 讀寫，對比其他非同步 I/O event loop，可能會有一點額外開銷。如果在一個單線程的程式中，使用 <code>strace</code> 會看到一個線程用 pipe 與自己溝通，很是奇怪：</p>
<pre><code class="language-c">pipe2([4, 5], O_NONBLOCK|O_CLOEXEC) = 0
...
epoll_wait(3, [{EPOLLIN|EPOLLOUT, {u32=14, u64=14}}], 1024, -1) = 1
write(5, &quot;\1&quot;, 1) = 1
epoll_wait(3, [{EPOLLIN, {u32=4294967295, u64=18446744073709551615}}], 1024, 0) = 1
read(4, &quot;\1&quot;, 128) = 1
read(4, 0x7ffce1140f58, 128) = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(12, &quot;hello\n&quot;, 1024, 0, {sa_family=AF_INET, sin_port=htons(43106), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [16]) = 6
recvfrom(12, 0x7f576621c800, 1024, 0, 0x7ffce1140070, 0x7ffce114011c) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(3, [], 1024, 0) = 0
epoll_wait(3, 0x7f5765b24000, 1024, -1) = -1 EINTR (Interrupted system call)
</code></pre>
<p>Mio 選用 pipe 的方案來支持通用性，以防 <code>set_readiness()</code> 可能被其他線程調用。也有可能這種作法對強制實施公平的事件調節與維持 futures 與 I/O 的間接層有所幫助。</p>
<h2 id="lessons-learned-combining-futures-vs-spawning-futures"><a class="header" href="#lessons-learned-combining-futures-vs-spawning-futures">Lessons learned: Combining futures vs. spawning futures</a></h2>
<p>最初探索 Tokio 時，我寫了一個小程式，負責監聽不同 UDP socket 進來的資料。這個程式建立十個讀取 socket 的 future 實例，每個都監聽不同的埠口（port）。我天真地使用 <a href="https://docs.rs/futures/0.1.17/futures/future/fn.join_all.html"><code>join_all()</code></a> 將所有 future 合成為單一 future，並將之傳入 <code>Core::run()</code>，訝異的是，我發現每當一個封包送達，所有 future 都會輪詢一次。另一個驚訝的點是，<code>tokio_core::net:UdpSocket::recv_from()</code>（以及底層的 <a href="https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.PollEvented.html"><code>PollEvented</code></a>）非常聰明，當 socket 在前一次的 Mio 輪詢中尚未標記為讀取就緒時，會避免調用作業系統 <code>rectfrom()</code>。以下的 <code>strace</code> 反映出我寫的 future <code>poll()</code> 的除錯 <code>println!()</code>，大致如下：</p>
<pre><code class="language-c">epoll_wait(3, [{EPOLLIN|EPOLLOUT, {u32=14, u64=14}}], 1024, -1) = 1
write(5, &quot;\1&quot;, 1) = 1
epoll_wait(3, [{EPOLLIN, {u32=4294967295, u64=18446744073709551615}}], 1024, 0) = 1
read(4, &quot;\1&quot;, 128) = 1
read(4, 0x7ffc183129d8, 128) = -1 EAGAIN (Resource temporarily unavailable)
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
recvfrom(12, &quot;hello\n&quot;, 1024, 0, {sa_family=AF_INET, sin_port=htons(43106), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [16]) = 6
getsockname(12, {sa_family=AF_INET, sin_port=htons(2006), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [16]) = 0
write(1, &quot;recv 6 bytes from 127.0.0.1:43106 at 127.0.0.1:2006\n&quot;, 52) = 52
recvfrom(12, 0x7f2a11c1c400, 1024, 0, 0x7ffc18312ba0, 0x7ffc18312c4c) = -1 EAGAIN (Resource temporarily unavailable)
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
write(1, &quot;UdpServer::poll()...\n&quot;, 21) = 21
epoll_wait(3, [], 1024, 0) = 0
epoll_wait(3, 0x7f2a11c36000, 1024, -1) = ...
</code></pre>
<p>有鑑於 Tokio 與 futures 的具體內部運作某個程度上對我來說有點隱晦，我想我希望背後有些魔法路由，可以只輪詢必要的 futures。當然，對 Tokio 有更深入的理解後，我的程式很明顯這樣利用 futures：</p>
<p><img src="assets/060.svg" alt="" /></p>
<p>這的確可以執行，但不夠好，尤其是當你有一拖拉庫 socket 時。由於通知在 task 層級發生，上圖中任意一個綠色方格中通知都會導致 main task 被通知。它將會輪詢 <code>FromAll</code> future 使得所有 <code>FromAll</code> 的 child future 都須接受輪詢。我真正需要的是一個簡單的 main future，使用 <code>Handle::spawn()</code> 來啟動每個封裝在各自的 task 中的 future。這種安排大致如下圖：</p>
<p><img src="assets/070.svg" alt="" /></p>
<p>當任何 future 安排一個通知，只有該 future 的 task 會收到通知，也只有該 future 會被輪詢（回想一下，「安排一個通知」會自動發生在 <code>tokio_core::net:UdpSocket::rect_from()</code> 從 <code>mio::net::UdpSocket::rect_from()</code> 回傳值中接收到 <code>WouldBlock</code> ）。future 組合操作子敘述表達能力強勁，可好整以暇地描述協議（protocol）的流程而不須弄髒手寫手動輪詢的狀態機。然而重要的是，你必須理解你的設計也許需要支援各自獨立，獨自且並行運作的 tasks。（譯注：而非都在 main task 上使用 <code>join_all()</code>）</p>
<h2 id="final-thoughts"><a class="header" href="#final-thoughts">Final thoughts</a></h2>
<p>閱讀 Tokio、Mio 以及 futures 原始碼後，大大幫助我鞏固對 Tokio 的理解，也驗證了透過理解具體實作來釐清抽象層的學習策略。這個方法在僅僅學習抽象層的狹隘使用案例時非常危險，我們必須意識到具體的示例僅是助於理解一般通例。在閱讀完原始碼之後，再次閱讀 Tokio 的教學文件，我有些馬後炮的意見：<strong>Tokio 非常合理，應該要很容易理解與上手！</strong></p>
<p>我仍有些問題待日後研究：</p>
<ul>
<li>Tokio 有處理 edge triggering（Linux <code>epoll</code>）的飢餓問題（starvation problem）嗎？我認為這個問題可以在 future 中，以單一一個 <code>poll()</code> 限制讀 / 寫的數量。當達到這個限制時，future 可以在顯式通知當前 task 提前返回，而非依靠 Tokio I/O 來源類型的隱式「<code>WouldBlock</code> 排程」行為。因此這使得其他 task 與 future 有機會有所進展。</li>
<li>Tokio 是否不依賴於將工作卸載給工作線程（worker thread）以最大化處理器核心運用，而是直接支援多線程環境下執行 event loop 嗎？</li>
</ul>
<blockquote>
<p><strong>2017-12-19 更新</strong>：這裡有 Reddit 對話串討論本文。Mio 的作者 Carl Lerche 在<a href="https://www.reddit.com/r/rust/comments/7klghl/tokio_internals_understanding_rusts_asynchronous/drfw5n1/">這裡</a>和<a href="https://www.reddit.com/r/rust/comments/7klghl/tokio_internals_understanding_rusts_asynchronous/drfwc1m/">這裡</a>貼了些資訊量充足的留言。除了回應上述問題，他也點出 <a href="https://docs.rs/futures/0.1.17/futures/stream/struct.FuturesUnordered.html"><code>FuturesUnordered</code></a> 是一種合成 futures 的方法，只有相關的 child future 會被輪詢，以避免所有 future 像使用 <code>join_all()</code> 全部輪詢，不過這方法有些額外的記憶體配置開銷要衡量。另外，未來的 Tokio 將要遷離 <code>mio::Registration</code> 的通知方案，目的是簡化前述一些步驟。</p>
<p><strong>2017-12-21 更新</strong>：看起來 Hacker News 也有在<a href="https://news.ycombinator.com/item?id=15972593">討論這篇文章</a>。</p>
</blockquote>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="../../09_源码分析/Tokio_internals/Tokio_internals.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next" href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="../../09_源码分析/Tokio_internals/Tokio_internals.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next" href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>




        <script type="text/javascript">
            window.playground_copyable = true;
        </script>


        <script src="../../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../searcher.js" type="text/javascript" charset="utf-8"></script>

        <script src="../../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->


    </body>
</html>
